package se.sll.itintegration.catalogue.gui.service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.validation.constraints.NotNull;
import javax.ws.rs.PathParam;

import lombok.extern.slf4j.Slf4j;

import org.apache.camel.ExchangePattern;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.cdi.CdiCamelContext;

import org.apache.shiro.*;
import se.sll.itintegration.catalogue.gui.camel.cdi.CamelRoute;
import se.sll.itintegration.catalogue.gui.data.CapacityResponse;
import se.sll.service.catalogue.common.exception.NotAuthorizedException;
import se.sll.service.catalogue.common.exception.ResourceNotFoundException;
import se.sll.service.catalogue.common.exception.ServiceErrorCode;
import se.sll.service.catalogue.common.exception.ServiceException;
import se.sll.service.catalogue.common.exception.ValidationException;
import se.sll.service.catalogue.common.rest.response.HsaData;
import se.sll.service.catalogue.gui.api.model.*;
import se.sll.service.catalogue.gui.api.model.Capacity;
import se.sll.service.catalogue.gui.api.model.Product;
import se.sll.service.catalogue.gui.api.model.ProductReference;
import se.sll.service.catalogue.gui.api.model.Unit;
import se.sll.service.catalogue.gui.api.resource.AggregatedService;

/**
 *
 * @author khaleddaham
 */
@Slf4j
@Stateless
@SuppressWarnings("unchecked")
public class AggregatedFacadeREST implements AggregatedService {

    @Inject
    private HsaCache hsaCache;
    @Inject
    private CdiCamelContext camelCtx;
    
    private static final String T_MEDIA_TYPE = "application/json; charset=UTF-8";
    
    @Override
    public AggregatedService.GetAggregatedTestResponse getAggregatedTest() throws NotAuthorizedException {
//        if(! SecurityUtils.isAllowed("test")) {
            throw new NotAuthorizedException(ServiceErrorCode.INVALID_ROLE, "");
//        }
//        return "ok";
    }

    /**
     *
     * @param hsaID
     * @return
     * @throws ValidationException
     * @throws ResourceNotFoundException
     */
    @Override
    public AggregatedService.GetAggregatedCapacityByHsaIDResponse getAggregatedCapacityByHsaID(
            @PathParam("hsaID") @NotNull String hsaID) throws ServiceException {
        log.debug("AggregatedFacadeREST.findCapacityByHsaID() ENTER");
        if (hsaID == null || "".equals(hsaID)) {
            throw new ValidationException(ServiceErrorCode.INVALID_HSA_ID_EMPTY);
        }
        try {
            Unit unitId = new Unit().withUnitId(hsaID);
            UnitNameList indata = new UnitNameList();
            indata.getUnits().add(unitId);
            List<ProductsByUnitList> result = getCapacityProducts(indata);
//            List<ProductsByUnitType> result = getCapacityProductsNoCamel(indata);
            log.debug("AggregatedFacadeREST.findCapacityByHsaID() EXIT");
            return GetAggregatedCapacityByHsaIDResponse.jsonOK(result);
        } catch (ServiceException ex) {
            log.error(hsaID, ex);
            throw new ResourceNotFoundException(ServiceErrorCode.CAPACITY_NOT_FOUND_HSAID, hsaID);
        }
    }

    /**
     *
     * @param entity
     * @return
     * @throws ValidationException
     * @throws ResourceNotFoundException
     */
    @Override
    public AggregatedService.PostAggregatedCapacityByunitidsResponse postAggregatedCapacityByunitids(UnitNameList entity) throws ServiceException {
        org.apache.shiro.SecurityUtils.getSubject().getSession().touch();
        try {
            if (entity.getUnits().isEmpty()) {
                return PostAggregatedCapacityByunitidsResponse.jsonOK(getAllCapacityProducts());
            } else {
                return PostAggregatedCapacityByunitidsResponse.jsonOK(getCapacityProducts(entity));
            }
        } catch (ServiceException e) {
            log.error("AggregatedFacadeREST.findCapacityByUnitIds() Caught Exception: Type: {}, Message: {} ",
                    new Object[]{e.getClass().getName(), e.getMessage()});
            log.error("Indata : {}", new Object[]{entity});
            throw new ResourceNotFoundException(ServiceErrorCode.CAPACITY_NOT_FOUND_UNIT_IDS, entity.toString());
        }
    }

    /**
     * @Todo remove
     * @return
     * @throws se.sll.service.catalogue.common.exception.ServiceException
     */
    @Override
    public AggregatedService.GetAggregatedUnitsResponse getAggregatedUnits() throws ServiceException {
        log.debug("AggregatedFacadeREST.readOffers() ENTER");
        long start = System.currentTimeMillis();

        ProducerTemplate template = camelCtx.createProducerTemplate();
        List<se.sll.service.catalogue.common.rest.model.Unit> offersData = (List<se.sll.service.catalogue.common.rest.model.Unit>) template.sendBodyAndHeader(CamelRoute.GET_OFFER_ALL, ExchangePattern.InOut, null, "path", "units");
        if (offersData == null) {
            throw new ResourceNotFoundException(ServiceErrorCode.OFFERS_NOT_FOUND);
        }

        for (se.sll.service.catalogue.common.rest.model.Unit unit : offersData) {
            HsaData hsaData = hsaCache.getHsa(unit.getUnitId());
            if (hsaData != null) {
                unit.setRelativeDistinguishedName(hsaData.getRelativeDistinguishedName());
            }
        }
        log.debug("AggregatedFacadeREST.readOffers(time = {} ms) EXIT", (System.currentTimeMillis() - start));
        return GetAggregatedUnitsResponse.jsonOK(convertUnitListToLocal(offersData));
    }

    /**
     * Converts a Unit between the common REST API and the local gui-api.
     *
     * @param commonUnitList
     * @return
     */
    private List<Unit> convertUnitListToLocal(List<se.sll.service.catalogue.common.rest.model.Unit> commonUnitList) {
        List<Unit> response = new ArrayList<>();
        for (se.sll.service.catalogue.common.rest.model.Unit currentUnit : commonUnitList) {

            Unit unit = new Unit()
                    .withRegionalUnitId(currentUnit.getRegionalUnitId())
                    .withUnitId(currentUnit.getUnitId())
                    .withCreationTime(currentUnit.getCreationTime())
                    .withRelativeDistinguishedName(currentUnit.getRelativeDistinguishedName())
                    .withUpdateTime(currentUnit.getUpdateTime());

            for (se.sll.service.catalogue.common.rest.model.ProductReference currentPr : currentUnit.getProductReferences()) {
                ProductReference pr = new ProductReference()
                        .withComment(currentPr.getComment())
                        .withContractedPrice(currentPr.getContractedPrice())
                        .withContractedProduct(currentPr.isContractedProduct());
                Product_ product = new Product_()
                        .withId(currentPr.getProduct().getDisplayName())
                        .withName(currentPr.getProduct().getName());
                pr.setProduct(product);

                for (se.sll.service.catalogue.common.rest.model.Capacity currentCapacity : currentPr.getCapacities()) {
                    Capacity capacity = new Capacity()
                            .withCode(currentCapacity.getCode())
                            .withCodeSystem(currentCapacity.getCodeSystem())
                            .withDisplayName(currentCapacity.getCode())
                            .withValidTo(currentCapacity.getValidTo())
                            .withValue(currentCapacity.getValue());
                    pr.getCapacities().add(capacity);
                }
                unit.getProductReferences().add(pr);
            }

            response.add(unit);
        }
        return response;
    }

    /**
     *
     * @param hsaId
     * @return
     * @throws se.sll.service.catalogue.common.exception.ServiceException
     */
    @Override
    public AggregatedService.GetAggregatedUnitByHsaIdResponse getAggregatedUnitByHsaId(
            @PathParam("hsaId") String hsaId) throws ServiceException {
        log.debug("AggregatedFacadeREST.readOffer() ENTER");
        long start = System.currentTimeMillis();
        ProducerTemplate template = camelCtx.createProducerTemplate();

        List<Unit> unitData = (List<Unit>) template.sendBodyAndHeader(CamelRoute.FROM_ONEOFFER, ExchangePattern.InOut, null, "hsaId", hsaId);
        Map<String, Product_> productMap = this.getProductMap();
        if (unitData == null) {
            throw new ResourceNotFoundException(ServiceErrorCode.PRODUCTS_NOT_FOUND);
        }

        for (Unit unit : unitData) {
            HsaData hsaData = hsaCache.getHsa(unit.getUnitId());
            if (hsaData != null) {
                unit.setRelativeDistinguishedName(hsaData.getRelativeDistinguishedName());
            } else {
                log.info("hsaData is null");
            }

            List<ProductReference> pRefList = new ArrayList<>();
            for (ProductReference prodRef : unit.getProductReferences()) {
                ProductReference pRef = new ProductReference();
                pRef.setCapacities(Collections.EMPTY_LIST);
                pRef.setComment(prodRef.getComment());
                pRef.setContractedPrice(prodRef.getContractedPrice());
                pRef.setProduct(productMap.get(prodRef.getProduct().getName()));
                pRefList.add(pRef);
            }
            unit.setProductReferences(pRefList);
        }
        log.info(unitData.toString());
        log.debug("AggregatedFacadeREST.readOffer(time = {} ms) EXIT", (System.currentTimeMillis() - start));
        return GetAggregatedUnitByHsaIdResponse.jsonOK(unitData);
    }

    /**
     * Aggregated services for idmap
     */
    /**
     *
     * @return
     * @throws se.sll.service.catalogue.common.exception.ServiceException
     */
    @Override
    public AggregatedService.GetAggregatedIdmapResponse getAggregatedIdmap() throws ServiceException {
        ProducerTemplate template = camelCtx.createProducerTemplate();
        List<IdmapList> idmapList = (List<IdmapList>) template.sendBody(CamelRoute.FROM_IDMAP, ExchangePattern.InOut, null);
        // HSA cache
        // @TODO move this to a processor
        for (IdmapList idmap : idmapList) {
            if (idmap.getHsaId() != null) {
                idmap.setHsaRelativeDistinguishedName(hsaCache.getHsa(idmap.getHsaId()).getRelativeDistinguishedName());
            }
        }
        return GetAggregatedIdmapResponse.jsonOK(idmapList);
    }

    /**
     *
     * @return @throws Exception
     */
    private List<ProductsByUnitList> getAllCapacityProducts() throws ServiceException {
        log.info("AggregatedFacadeREST.getAllCapacityProducts() ENTER");
        long start = System.currentTimeMillis();

        ProducerTemplate template = camelCtx.createProducerTemplate();
        long offersStart = System.currentTimeMillis();
        List<se.sll.service.catalogue.common.rest.model.Unit> units = (List<se.sll.service.catalogue.common.rest.model.Unit>)
                template.sendBodyAndHeader(CamelRoute.GET_OFFER_ALL, ExchangePattern.InOut, null, "path", "units");
        log.info("AggregatedFacadeREST.getAllCapacityProducts() : Offers response time = {} ms", (System.currentTimeMillis() - offersStart));
        if (units == null) {
            throw new ResourceNotFoundException(ServiceErrorCode.UNITS_NOT_FOUND);
        }
        long capacityStart = System.currentTimeMillis();
        List<CapacityResponse> capacityData = (List<CapacityResponse>) template.sendBodyAndHeader(CamelRoute.FROM_CAPACITY, ExchangePattern.InOut, null, "", "");
        log.info("AggregatedFacadeREST.getAllCapacityProducts() : Capacity response time = {} ms", (System.currentTimeMillis() - capacityStart));
        if (capacityData == null) {
            throw new ResourceNotFoundException(ServiceErrorCode.CAPACITIES_NOT_FOUND);
        }
        List<ProductsByUnitList> result = buildAggregatedProductCapacityList(units, capacityData);
        log.info("AggregatedFacadeREST.getAllCapacityProducts(time = {} ms) EXIT", (System.currentTimeMillis() - start));
        return result;
    }

    /**
     * @TODO get HSA info
     * @return
     * @throws Exception
     */
    private List<ProductsByUnitList> getCapacityProducts(UnitNameList unitIds) throws ServiceException {
        log.info("AggregatedFacadeREST.getCapacityProducts() ENTER");
        long start = System.currentTimeMillis();

        ProducerTemplate template = camelCtx.createProducerTemplate();
        long offersStart = System.currentTimeMillis();
        List<se.sll.service.catalogue.common.rest.model.Unit> offersData = (List<se.sll.service.catalogue.common.rest.model.Unit>) template.sendBodyAndHeader(CamelRoute.FROM_OFFERS_BYUNITIDS, ExchangePattern.InOut, unitIds, "Content-Type", "application/json");
        log.info("AggregatedFacadeREST.getCapacityProducts() : Offers response time = {} ms", (System.currentTimeMillis() - offersStart));

        if (offersData == null) {
            throw new ResourceNotFoundException(ServiceErrorCode.OFFERS_NOT_FOUND);
        }
        long capacityStart = System.currentTimeMillis();
        List<CapacityResponse> capacityData = (List<CapacityResponse>) template.sendBodyAndHeader(CamelRoute.FROM_CAPACITY_BYUNITIDS, ExchangePattern.InOut, unitIds, "Content-Type", "application/json");
        log.info("AggregatedFacadeREST.getCapacityProducts() : Capacity response time = {} ms", (System.currentTimeMillis() - capacityStart));
        if (capacityData == null) {
            throw new ResourceNotFoundException(ServiceErrorCode.CAPACITIES_NOT_FOUND);
        }

        List<ProductsByUnitList> result = buildAggregatedProductCapacityList(offersData, capacityData);

        log.info("AggregatedFacadeREST.getCapacityProducts(time = {} ms) EXIT", (System.currentTimeMillis() - start));
        return result;
    }

    /**
     * @TODO merge this into a camel route
     * @param offers
     * @param capacities
     */
    private List<ProductsByUnitList> buildAggregatedProductCapacityList(List<se.sll.service.catalogue.common.rest.model.Unit> offers, List<CapacityResponse> capacities) {
        log.debug("AggregatedFacadeREST.buildAggregatedProductCapacityList() ENTER");
        long start = System.currentTimeMillis();

        List<ProductsByUnitList> result = new ArrayList<>();
        for (se.sll.service.catalogue.common.rest.model.Unit unit : offers) {
            ProductsByUnitList p = new ProductsByUnitList();
            p.setHsaID(unit.getUnitId());
            p.setName(this.getHSAName(unit.getUnitId()));
            List<Product> productList = new ArrayList<>();
            CapacityResponse unitCapacity = getCapacityResponseForUnit(unit.getUnitId(), capacities);
            for (se.sll.service.catalogue.common.rest.model.ProductReference pref : unit.getProductReferences()) {
                Product pt = new Product();
                pt.setCapacity(getCapacityList(pref.getProduct().getName(), unitCapacity));
                pt.setName(pref.getProduct().getName());
                pt.setId(pref.getProduct().getName());
                productList.add(pt);
            }
            p.setProducts(productList);
            result.add(p);
        }
        log.debug("AggregatedFacadeREST.buildAggregatedProductCapacityList(time = {} ms) EXIT", (System.currentTimeMillis() - start));
        return result;
    }

    /**
     * @return
     */
    private String getHSAName(String hsaID) {
        HsaData hsaData = hsaCache.getHsa(hsaID);
        if (hsaData == null) {
            return "";
        }
        return hsaData.getRelativeDistinguishedName();
    }

    /**
     * @TODO chance capacitiesForUnit to a hashmap with product name as key.
     * @param productName
     * @param capacitiesForUnit
     * @return
     */
    private List<Capacity> getCapacityList(String productName, CapacityResponse capacitiesForUnit) {
        List<Capacity> capacityList = new ArrayList<>();
        if (capacitiesForUnit != null) {
            List<se.sll.itintegration.catalogue.gui.data.Product> pList = capacitiesForUnit.getProduct();
            for (se.sll.itintegration.catalogue.gui.data.Product p : pList) {
                if (p.getName().equals(productName)) {
                    for (se.sll.itintegration.catalogue.gui.data.Capacity c : p.getCapacity()) {
                        Capacity ct = new Capacity();
                        ct.setCode(c.getCode());
                        ct.setCodeSystem(c.getCodeSystem());
                        ct.setDisplayName("");
                        ct.setValidTo(c.getValidTo());
                        ct.setValue(c.getValue());
                        capacityList.add(ct);
                    }
                }
            }
        }
        return capacityList;
    }

    /**
     *
     * @param unitId
     * @param capacities
     * @return
     */
    private CapacityResponse getCapacityResponseForUnit(String unitId, List<CapacityResponse> capacities) {
        CapacityResponse result = null;
        for (CapacityResponse capacityResponse : capacities) {
            if (unitId.equalsIgnoreCase(capacityResponse.getHsaID())) {
                result = capacityResponse;
            }
        }
        return result;
    }

    /**
     *
     * @return @throws Exception
     */
    private Map<String, Product_> getProductMap() {
        ProducerTemplate template = camelCtx.createProducerTemplate();
        Map<String, Product_> map
                = (HashMap<String, Product_>) template.sendBodyAndHeader(CamelRoute.FROM_PRODUCTREAD, ExchangePattern.InOut, null, "queryparam", "");
        return map;
    }
}
